package com.mspbots.rule.exceptions;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mspbots.api.client.CoreClient;
import com.mspbots.api.client.WiseClient;
import com.mspbots.api.config.ApiProperties;
import com.mspbots.api.service.MessagesService;
import com.mspbots.api.service.TenantConfigurationService;
import com.mspbots.api.service.TenantUserMappingService;
import com.mspbots.api.service.TenantUserService;
import com.mspbots.rule.queue.producer.MessageProducer;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.annotation.Priority;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;

import static com.mspbots.cw.request.MessagesDTO.TYPE_USER;


/**
 * com.mspbots.rule.exceptions.BaseRule
 *
 * @author Jun Zhou
 * @description
 * @date 13:57 2019/6/18
 **/
@Slf4j
public abstract class BaseRule {

    public static final String REDIS_KEY_PREFIX_ESCALATION = "MSPBOTS:CONFIG:ESCALATION:";
    public static final String REDIS_KEY_PREFIX_TENANT = "MSPBOTS:CONFIG:TENANT:";
    public static final String FACT = "FACT";
    protected static final String KEYWORD_WISE_SITE = "mspbots.sync.wise.site";
    protected static String MESSAGE_PARAM_STYLE = "<span style='color:#FF0000;font-weight:bold'>{}</span>";
    protected StringRedisTemplate stringRedisTemplate;
    protected MessagesService messagesService;
    protected TenantUserMappingService tenantUserMappingService;
    protected TenantUserService tenantUserService;
    protected TenantRuleDTO tenantRuleDTO;
    protected TriggerEscalationDTO triggerEscalationDTO;
    protected TenantDTO tenantDTO;
    protected MessagesDTO messages;
    protected ObjectMapper objectMapper;
    protected Map<String, Object> params;
    protected TenantUsersMappingDTO tenantUserMappingDTO;
    protected TenantConfigurationService tenantConfigurationService;
    protected JSONObject userWorkTimeConfig;
    protected ApiProperties apiProperties;
    protected RestTemplate restTemplate;

    protected CoreClient coreClient;
    protected WiseClient wiseClient;

    protected MessageProducer messageProducer;
    private int priority = Integer.MAX_VALUE;

    public BaseRule() {
    }

    public void init(TenantRuleDTO tenantRuleDTO, MessageProducer messageProducer,
                     MessagesService messagesService, TenantUserMappingService tenantUserMappingService,
                     TenantConfigurationService tenantConfigurationService, CoreClient coreClient, ObjectMapper objectMapper) {
        this.tenantRuleDTO = tenantRuleDTO;
        this.messagesService = messagesService;
        this.messageProducer = messageProducer;
        this.tenantUserMappingService = tenantUserMappingService;
        this.tenantConfigurationService = tenantConfigurationService;
        this.coreClient = coreClient;
        this.objectMapper = objectMapper;
        messages = new MessagesDTO();
        this.tenantRuleDTO.setTemplate(StringUtils.isEmpty(this.tenantRuleDTO.getTemplate()) ? this.tenantRuleDTO.getSysTemplate()
                : this.tenantRuleDTO.getTemplate());
        this.tenantRuleDTO.setRuleParams(JSONUtil.isNull(this.tenantRuleDTO.getRuleParams()) ?
                this.tenantRuleDTO.getSysParams() : this.tenantRuleDTO.getRuleParams());
    }

    public void init(MessageProducer messageProducer,
                     MessagesService messagesService, TenantUserMappingService tenantUserMappingService,
                     TenantConfigurationService tenantConfigurationService, CoreClient coreClient, ObjectMapper objectMapper) {
        this.messagesService = messagesService;
        this.messageProducer = messageProducer;
        this.tenantUserMappingService = tenantUserMappingService;
        this.tenantConfigurationService = tenantConfigurationService;
        this.coreClient = coreClient;
        this.objectMapper = objectMapper;
        messages = new MessagesDTO();
    }

    protected String getConfiguration(String keyword) {
        TenantConfigurationDTO tenantConfiguration = tenantConfigurationService.findByKeyword(tenantRuleDTO.getTenantId(), keyword);
        return ObjectUtils.isEmpty(tenantConfiguration) ? "" : tenantConfiguration.getKeywordValue();
    }


    /**
     * @return boolean  true:has message  false: no message
     * @Author Jun
     * @Description TODO
     * @Date 2019/8/14 15:05
     * @Param [messages]
     **/
    protected boolean checkMessage(MessagesDTO messages) {
        messages.setRuleId(tenantRuleDTO.getRuleId());
        messages.setTenantId(tenantRuleDTO.getTenantId());
        messages.setTeamsUserId(tenantUserMappingDTO.getTeamsUserId());
        return messagesService.check(messages);
    }


    public Flux<JsonNode> hasMessage(MessagesDTO messages) {
        messages.setRuleId(tenantRuleDTO.getRuleId());
        messages.setTenantId(tenantRuleDTO.getTenantId());
        messages.setTeamsUserId(tenantUserMappingDTO.getTeamsUserId());
        Map<String, Object> queryParams = new HashMap<>(16) {{
            put("ruleId", messages.getRuleId());
            put("tenantId", messages.getTenantId());
            put("timeEntryId", messages.getTimeEntryId());
            put("tsheetTimesId", messages.getTsheetTimesId());
            put("createDate", messages.getCreateDate());
            put("userId", messages.getUserId());
        }};
        return this.coreClient.getFlux("/teams/messages/countMessagesByForRule", queryParams);

    }

    protected boolean checkDayMessage(Long tenantUserId) {
        Map<String, Object> queryParams = new HashMap<>(16) {{
            put("ruleId", tenantRuleDTO.getRuleId());
            put("tenantId", messages.getTenantId());
            put("timeEntryId", messages.getTimeEntryId());
            put("tsheetTimesId", messages.getTsheetTimesId());
            put("userId", tenantUserId);
        }};

        Mono<JsonNode> resultMono = this.coreClient.get("/teams/messages/list", queryParams);
        JsonNode result = resultMono.block();

        List<MessagesDTO> messagesList = this.objectMapper.convertValue(result, new TypeReference<List<MessagesDTO>>() {
        });
        if (tenantRuleDTO.getRuleId() == 8) {
            if (messagesList != null && messagesList.size() > 0) {
                return messagesList.stream()
                        .filter(item -> item.getCreateDate().isAfter(LocalDateTime.now().minusHours(15)))
                        .collect(Collectors.toList())
                        .size() > 0;
            }
        } else {
            if (messagesList != null && messagesList.size() > 0) {
                return messagesList.stream()
                        .filter(item -> item.getCreateDate().isAfter(LocalDateTime.now().minusHours(24)))
                        .collect(Collectors.toList())
                        .size() > 0;
            }
        }

        return false;

    }

    protected boolean setUserMapping(Long tenantUserId) {
        tenantUserMappingDTO = this.tenantUserMappingService.findByTenantUser(tenantRuleDTO.getTenantId(), tenantUserId);
        return tenantUserMappingDTO != null && !StringUtils.isEmpty(tenantUserMappingDTO.getTenantUserId());
    }

//    protected Mono<TenantUsersMappingDTO> setUserMapping(Long tenantUserId) {
//        return this.coreClient.get("/user-mapping/user",
//                Map.of("tenantId", tenantRuleDTO.getTenantId(), "tenantUserId", tenantUserId),TenantUsersMappingDTO.class);
//    }

    protected boolean setUserMapping(Long tenantUserId, Long tenantId) {
        tenantUserMappingDTO = this.tenantUserMappingService.findByTenantUser(tenantId, tenantUserId);
        return tenantUserMappingDTO != null && !StringUtils.isEmpty(tenantUserMappingDTO.getTenantUserId());
    }

    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void setWiseClient(WiseClient wiseClient) {
        this.wiseClient = wiseClient;
    }


    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    protected boolean insertMessage(MessagesDTO messages) {
        messages.setTeamsUserId(tenantUserMappingDTO.getTeamsUserId());
        messages.setRuleId(this.tenantRuleDTO.getRuleId());
        messages.setTenantId(this.tenantRuleDTO.getTenantId());
        messages.setUserId(tenantUserMappingDTO.getTenantUserId());
        messages.setStatus("Ready");
        messages.setForwardStatus("Ready");
        messages.setType(TYPE_USER);
        messages.setExpireDate(LocalDateTime.now().plusHours(12));
//        log.info("insert msg : {}",messages);
        MessagesDTO messagesResult = messagesService.insert(messages);
//        if (messagesResult != null) {
//            messageProducer.produce(messagesResult);
//        }

        return true;
    }

    protected StringBuilder formatMessage(boolean styleParam) {
        StringBuilder message = new StringBuilder();

        this.params = new HashMap<>(16);

        this.params.put("USER", tenantUserMappingDTO.getFirstName());


        if (tenantRuleDTO.getRuleParams() != null && !tenantRuleDTO.getRuleParams().isNull()) {
            if (styleParam) {
                Iterator<String> iterator = tenantRuleDTO.getRuleParams().fieldNames();
                while (iterator.hasNext()) {
                    String fieldName = iterator.next();
                    params.put(fieldName, styleParam(tenantRuleDTO.getRuleParams().get(fieldName).asText()));
                }
            } else {
                this.params.putAll(this.objectMapper.convertValue(tenantRuleDTO.getRuleParams(), Map.class));
            }
        }

        message.append(StrUtil.format(tenantRuleDTO.getTemplate(), this.params)).append("<br>").append(suffixEscalation());
        return message;
    }

    protected String suffixEscalation() {
        String escalationCacheKey = REDIS_KEY_PREFIX_ESCALATION + tenantRuleDTO.getTenantId() + ":" + tenantUserMappingDTO.getTenantUserId() + "_" + tenantRuleDTO.getRuleId();
        String times = stringRedisTemplate.opsForValue().get(escalationCacheKey);
        if (StringUtils.isEmpty(times)) {
            times = "0";
        }
        Integer timesAfter = Integer.parseInt(times) + 1;
        stringRedisTemplate.opsForValue().set(escalationCacheKey, String.valueOf(timesAfter));
        String following = "";
        Map<String, Object> params = Map.of("tenantId", tenantRuleDTO.getTenantId(), "userId", tenantUserMappingDTO.getTenantUserId(),
                "ruleId", tenantRuleDTO.getRuleId(), "escalation", false);
        int count = messagesService.getRuleCount(params) + 1;
        if (!StringUtils.isEmpty(tenantRuleDTO.getTimes())) {
            following = "--[ <span style='color:#999d9c;'>" + count + (count > 1 ? " times" : " time") + "</span> this week. Threshold <span style='color:#999d9c;'>" + tenantRuleDTO.getTimes() + "</span> ]";
        }
        return following;
    }

    protected StringBuilder suffixUserName() {
        return new StringBuilder().append("[").append(tenantUserMappingDTO.getUserName()).append("]&nbsp;");
    }

    protected StringBuilder suffixCwZoneTime(LocalDateTime enteredDateTime) {
        return new StringBuilder().append("[")
                .append(enteredDateTime.plusHours(Integer.parseInt(tenantUserMappingDTO.getTz().substring(0, 3))).format(DateTimeFormatter.ofPattern("MM/dd/yy HH:mm")))
                .append("/")
                .append(tenantUserMappingDTO.getTzStr())
                .append("]");

    }

    protected StringBuilder formatZoneTime(LocalDateTime dateTime) {
        return new StringBuilder()
                .append(dateTime.plusHours(Integer.parseInt(tenantUserMappingDTO.getTz().substring(0, 3))).format(DateTimeFormatter.ofPattern("MM/dd HH:mm")))
                .append("/")
                .append(tenantUserMappingDTO.getTzStr());

    }

    protected String buildMessage(LocalDateTime localDateTime) {
        return new StringBuilder()
                .append(formatMessage(true))
                .append(suffixMessage(localDateTime))
                .toString();
    }

    protected String suffixMessage(LocalDateTime localDateTime) {
        return new StringBuilder()
                .append("--")
                .append("<span style='color:#999d9c;font-size:10px;'>")
                .append(suffixUserName())
                .append(suffixCwZoneTime(localDateTime))
                .append("</span>").toString();
    }


    protected StringBuilder suffixAttendanceZoneTime(LocalDateTime enteredDateTime) {
        log.info("suffixAttendanceZoneTime_{} {} {}", this.tenantRuleDTO.getRuleId(), Integer.parseInt(userWorkTimeConfig.getStr("offset", "+00:00").substring(0, 3)), enteredDateTime);
        return new StringBuilder().append("[")
                .append(enteredDateTime.plusHours(Integer.parseInt(userWorkTimeConfig.getStr("offset", "+00:00").substring(0, 3))).format(DateTimeFormatter.ofPattern("MM/dd/yy HH:mm")))
                .append("/")
                .append(userWorkTimeConfig.getStr("timezone"))
                .append("]");


    }


    protected String styleParam(String param) {
        return StrUtil.format(MESSAGE_PARAM_STYLE, param);
    }


    /**
     * 获取优先级
     */
    @Priority
    public int getPriority() {
        return priority;
    }

    public void setPriority(int priority) {
        this.priority = priority;
    }


    @Override
    public boolean equals(Object obj) {
        return Objects.nonNull(obj)
                && Objects.equals(this.getClass().getName(), obj.getClass().getName());
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(this.getClass().getName());
    }


}
